package top.lywivan.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.UnorderedThreadPoolEventExecutor;
import top.lywivan.netty.codec.ProtoStuffDecoder;
import top.lywivan.netty.codec.ProtoStuffEncoder;
import top.lywivan.netty.handler.server.ServerReadIdleHandler;
import top.lywivan.netty.handler.server.TcpStickHalfHandler1;

/**
 * @author :lyw.ivan
 * @date :Created in 2022/8/13
 * @description : netty服务端
 * @version: 1.0
 */
public class NettyServer {
    public static void main(String[] args) {
        NettyServer nettyServer = new NettyServer();
        nettyServer.start(8888);
    }

    private void start(int port){
        //使用主从Reactor多线程模型，创建两个group，一个是做注册事件监听的Reactor的group，一个是做IO事件监听的Reactor的group
        //并给每一个线程池group设置名称，便于调试
        EventLoopGroup bossGroup=new NioEventLoopGroup(1,new DefaultThreadFactory("boss"));
        EventLoopGroup workerGroup=new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
        //定义处理业务逻辑的线程池，设置核心线程数为cpu核心数的2倍
        //定义线程池的名称为business，便于调试
        EventExecutorGroup business=new UnorderedThreadPoolEventExecutor(NettyRuntime.availableProcessors()*2,new DefaultThreadFactory("business"));
        //创建服务器引导类
        ServerBootstrap serverBootstrap=new ServerBootstrap();
        //创建客户端公用的Handler，避免创建过多handler
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG);
        serverBootstrap
                //设置启用引导的分组，决定其使用的Reactor模型
                .group(bossGroup,workerGroup)
                //指定服务端的Channel对象
                .channel(NioServerSocketChannel.class)
                //设置当服务端无法接受连接时，最大可以保存的连接数
                .option(ChannelOption.SO_BACKLOG,1024)
                //设置客户端是否进行心跳检测
                .childOption(ChannelOption.SO_KEEPALIVE,true)
                //关闭TCP的nagle算法，避免延迟
                .childOption(ChannelOption.TCP_NODELAY,true)
                //设置做注册事件监听的Reactor的group对应的pipeline中的handler，此处只需要做日志记录
                .handler(new LoggingHandler(LogLevel.DEBUG))
                //配置做IO事件监听的Reactor的group对应的pipeline中的handler，自己有head和tail，此处配置自定义的handler回调
                //使用了责任链的设计模式
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //获取channel对应的pipeline
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //绑定pipeline对应的自定义的Handler，需要注意Handler的先后顺序
                        //优先添加日志handler
                        pipeline.addLast(loggingHandler);
                        //设置客户端的空闲检测handler，当空闲一定时间，主动断开连接，释放资源
                        pipeline.addLast(new ServerReadIdleHandler(10,0,0));
                        //设置一次编码，用于解决TCP粘包拆包问题，相当于规定应用层协议
                        //这里采用固定长度字段存消息字段的方式解决，header长度为4字节
                        pipeline.addLast(new LengthFieldPrepender(4));
                        //设置二次编码，将数据进行序列化，便于业务直接获取对象类型，避免强转
                        //这里采用ProtoStuff的序列化方案，体积更小，效率更高，且操作方便
                        pipeline.addLast(new ProtoStuffEncoder());
                        //设置一次解码，将数据拆包
                        //五个参数依次表示：1.解码最长长度 2.前面是否需要跳过字节 3.header占用的字节长度 4.header和body之间是否有其他字节 5.解码后是否跳过字节读取
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(65536,0,4,0,4));
                        //设置二次解码，将数据反序列化
                        pipeline.addLast(new ProtoStuffDecoder());
                        //处理具体的业务逻辑，需要指定使用的业务线程池
                        pipeline.addLast(business,new TcpStickHalfHandler1());
                    }
                })
        ;
        //启动服务端的引导类，同时进行阻塞等待。获取future对象
        try {
            ChannelFuture future = serverBootstrap.bind(port).sync();
            //监听端口的关闭事件，并进行阻塞
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //关闭线程池相关资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            business.shutdownGracefully();
        }
    }
}
